﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using AvalonDock;
using ICSharpCode.AvalonEdit.Highlighting;
using System.Xml;
using System.IO;
using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Editing;
using System.Windows.Threading;
using tiDE.Support.CodeInsight;

namespace tiDE.Panels
{
    /// <summary>
    /// Interaction logic for CodePanel.xaml
    /// </summary>
    public partial class CodePanel : DockableContent
    {
        public List<int> BreakpointLines;
        BreakpointMargin breakpointMargin = new BreakpointMargin();
        BreakpointRenderer breakpointRenderer = new BreakpointRenderer();

        public CodePanel()
        {
            InitializeComponent();
            BreakpointLines = new List<int>();
            TextEditor.TextArea.TextView.BackgroundRenderers.Add(breakpointRenderer);
            TextEditor.TextArea.TextView.LineTransformers.Add(breakpointRenderer);
            TextEditor.TextArea.LeftMargins.Insert(0, breakpointMargin);
            breakpointMargin.MouseLeftButtonDown += new MouseButtonEventHandler(breakpointMargin_MouseLeftButtonDown);
        }

        void breakpointMargin_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            DocumentLine line = TextEditor.Document.GetLineByNumber(GetLineFromMousePosition(e));
            Breakpoint bp = new Breakpoint()
            {
                StartOffset = line.Offset,
                EndOffset = line.EndOffset,
                Length = line.Length,
                Active = false,
                Enabled = true,
            };
            ToggleBreakpoint(line.LineNumber);
            if (e.ChangedButton == MouseButton.Left)
                e.Handled = true;
        }

        int GetLineFromMousePosition(MouseEventArgs e)
        {
            TextView textView = breakpointMargin.TextView;
            if (textView == null)
                return 0;
            VisualLine vl = textView.GetVisualLineFromVisualTop(e.GetPosition(textView).Y + textView.ScrollOffset.Y);
            if (vl == null)
                return 0;
            return vl.FirstDocumentLine.LineNumber;
        }

        public static void LoadHighlighting(string HighlightingFile, string Name, string[] Extensions)
        {
            IHighlightingDefinition customHighlighting;
            using (Stream s = File.Open(HighlightingFile, FileMode.Open))
            {
                if (s == null)
                    throw new InvalidOperationException("Could not find highlighting file");
                using (XmlReader reader = new XmlTextReader(s))
                {
                    customHighlighting = ICSharpCode.AvalonEdit.Highlighting.Xshd.
                        HighlightingLoader.Load(reader, HighlightingManager.Instance);
                }
            }
            HighlightingManager.Instance.RegisterHighlighting(Name, Extensions, customHighlighting);
        }

        public void ToggleBreakpoint(int Line)
        {
            DocumentLine docLine = TextEditor.Document.GetLineByNumber(Line);
            Breakpoint bp;

            if (CodeInsightManager.GetProviderByName(TextEditor.SyntaxHighlighting.Name) == null)
            {
                bp = new Breakpoint()
                {
                    StartOffset = docLine.Offset,
                    EndOffset = docLine.EndOffset,
                    Length = docLine.Length,
                    Active = false,
                    Enabled = true,
                };
            }
            else
            {
                bp = CodeInsightManager.GetProviderByName(TextEditor.SyntaxHighlighting.Name).GetBreakpointForLine(
                    TextEditor.Document.GetText(docLine.Offset, docLine.Length));
                bp.StartOffset += docLine.Offset;
                bp.EndOffset = bp.StartOffset + bp.Length;
            }

            foreach (Breakpoint ts in breakpointMargin.BreakpointSegments)
            {
                if (ts.StartOffset == bp.StartOffset && ts.Length == bp.Length)
                {
                    // Remove breakpoint
                    breakpointMargin.BreakpointSegments.Remove(ts);
                    breakpointRenderer.BreakpointSegments = breakpointMargin.BreakpointSegments;
                    breakpointMargin.InvalidateVisual();
                    TextEditor.TextArea.TextView.Redraw(bp, DispatcherPriority.Normal);
                    return;
                }
            }

            breakpointMargin.BreakpointSegments.Add(bp);
            breakpointRenderer.BreakpointSegments.Add(new Breakpoint()
            {
                StartOffset = bp.StartOffset,
                EndOffset = bp.EndOffset,
                Length = bp.Length,
                Active = bp.Active,
                Enabled = bp.Enabled,
            });
            breakpointMargin.InvalidateVisual();
            TextEditor.TextArea.TextView.Redraw(bp, DispatcherPriority.Normal);
        }
    }

    #region AvalonEdit Extensions

    class BreakpointRenderer : DocumentColorizingTransformer, IBackgroundRenderer
    {
        public TextSegmentCollection<Breakpoint> BreakpointSegments;

        public BreakpointRenderer()
        {
            BreakpointSegments = new TextSegmentCollection<Breakpoint>();
        }

        public void Draw(TextView textView, DrawingContext drawingContext)
        {
            foreach (Breakpoint bp in BreakpointSegments)
            {
                BackgroundGeometryBuilder bgb = new BackgroundGeometryBuilder();
                bgb.AlignToWholePixels = true;
                bgb.CornerRadius = 3;
                bgb.AddSegment(textView, bp);
                Geometry geo = bgb.CreateGeometry();
                if (geo != null)
                {
                    Color color = bp.Active ? Colors.Yellow : Colors.Maroon;
                    SolidColorBrush brush = new SolidColorBrush(color);
                    brush.Freeze();
                    drawingContext.DrawGeometry(brush, null, geo);
                }
            }
        }

        public KnownLayer Layer
        {
            get { return KnownLayer.Background; }
        }

        protected override void ColorizeLine(DocumentLine line)
        {
            int lineStart = line.Offset;
            int lineEnd = lineStart + line.Length;
            foreach (Breakpoint marker in BreakpointSegments.FindOverlappingSegments(lineStart, line.Length))
            {
                Brush foregroundBrush = null;
                foregroundBrush = new SolidColorBrush(marker.Active ? Colors.Black : Colors.White);
                foregroundBrush.Freeze();
                ChangeLinePart(
                    Math.Max(marker.StartOffset, lineStart),
                    Math.Min(marker.EndOffset, lineEnd),
                    element =>
                    {
                        if (foregroundBrush != null)
                        {
                            element.TextRunProperties.SetForegroundBrush(foregroundBrush);
                        }
                    }
                );
            }
        }
    }

    class BreakpointMargin : AbstractMargin
    {
        public TextSegmentCollection<Breakpoint> BreakpointSegments;

        public BreakpointMargin()
        {
            BreakpointSegments = new TextSegmentCollection<Breakpoint>();
        }

        protected override void OnTextViewChanged(TextView oldTextView, TextView newTextView)
        {
            if (oldTextView != null)
                oldTextView.VisualLinesChanged -= new EventHandler(newTextView_VisualLinesChanged);
            if (newTextView != null)
                newTextView.VisualLinesChanged += new EventHandler(newTextView_VisualLinesChanged);
            InvalidateVisual();
        }

        void newTextView_VisualLinesChanged(object sender, EventArgs e)
        {
            if (this.TextView != null && this.TextView.VisualLinesValid)
            {
                InvalidateVisual();
            }
        }

        protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
        {
            // accept clicks even when clicking on the background
            return new PointHitTestResult(this, hitTestParameters.HitPoint);
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            return new Size(18, 0);
        }

        protected override void OnRender(DrawingContext drawingContext)
        {
            drawingContext.DrawRectangle(Brushes.LightGray, null,
                new Rect(0, 0, RenderSize.Width, RenderSize.Height));
            drawingContext.DrawLine(new Pen(Brushes.Gray, 1),
                new Point(RenderSize.Width, 0), new Point(RenderSize.Width, RenderSize.Height));
            if (this.TextView != null)
            {
                foreach (VisualLine line in this.TextView.VisualLines)
                {
                    if (BreakpointSegments.FindOverlappingSegments(line.FirstDocumentLine).Count != 0)
                    {
                        Breakpoint bp = BreakpointSegments.FindOverlappingSegments(line.FirstDocumentLine).First();
                        // Draw a breakpoint here
                        BitmapImage backgroundImage;
                        if (bp.Enabled)
                            backgroundImage = new BitmapImage(new Uri("pack://application:,,,/tiDE;component/Images/Icons/Breakpoint.png"));
                        else
                            backgroundImage = new BitmapImage(new Uri("pack://application:,,,/tiDE;component/Images/Icons/DisabledBreakpoint.png"));
                        drawingContext.DrawImage(backgroundImage,
                                new Rect(0, line.VisualTop - this.TextView.VerticalOffset, 16, 16));
                        if (bp.Active)
                            drawingContext.DrawImage(new BitmapImage(new Uri("pack://application:,,,/tiDE;component/Images/Icons/Icons.16x16.Debug.Start.png")),
                                new Rect(0, line.VisualTop - this.TextView.VerticalOffset, 16, 16));
                    }
                }
            }
            base.OnRender(drawingContext);
        }
    }

    public class Breakpoint : TextSegment
    {
        public bool Enabled { get; set; }
        public bool Active { get; set; }
    }
    #endregion
}
